1. 服务是什么?
服务是提供了特定功能的类。例如,邮件服务、日志服务、哈希服务等。在 Laravel 中,这些服务由服务容器进行统一管理,确保它们能够被正确初始化和依赖注入。
2. Laravel 服务提供者是什么?
服务提供者是 Laravel 应用启动的核心机制。每个服务提供者包含两个主要方法:
3. 能否不用服务提供者,直接调用服务?
可以。得益于 Laravel 的依赖注入机制,您可以在控制器、作业等地方通过类型提示来直接解析并使用服务:
|
|
public function store(Request $request, MailService $mailService) { $mailService->sendWelcomeEmail($request->user()); } |
但是,服务提供者是告诉容器如何构建该服务的标准位置。对于有复杂依赖的类,没有服务提供者的注册,容器可能无法正确解析。
4. 服务提供者的必要性
尽管可以直接调用服务,服务提供者仍然至关重要:
a. 全局引导配置
通过 boot 方法进行全局设置,如视图 Composer、路由注册、事件监听等:
|
|
public function boot() { view()->composer('sidebar', function ($view) { $view->with('categories', Category::all()); }); } |
b. 实现接口绑定与解耦
服务提供者通过 bind 或 singleton 实现接口到具体实现的绑定:
|
|
public function register() { $this->app->singleton( Illuminate\Contracts\Mail\Mailer::class, App\Services\CustomMailer::class ); } |
这种增加的复杂性带来了松耦合和灵活性的优势,使得替换实现变得简单。
选择建议: 默认情况下,对于重量级或有状态的服务使用 singleton,对于轻量级或无状态的服务使用 bind。根据具体业务需求和性能要求做出合适选择。
这段代码的作用是:告诉 Laravel 容器,当代码中需要 Mailer 接口时,应该提供 CustomMailer 类的单例实例。
|
|
// 门面自动工作 Mail::send(...); // 底层自动解析 Mailer 接口的绑定 |
一个接口不能直接绑定两个实例,但 Laravel 提供了多种方式来实现根据条件返回不同实例的需求。
1. 上下文绑定(Contextual Binding)
根据使用场景动态选择实现:
|
|
public function register() { // 当 OrderService 需要 Mailer 时,使用事务邮件 $this->app->when(OrderService::class) ->needs(Mailer::class) ->give(TransactionMailer::class); // 当 NewsletterService 需要 Mailer 时,使用营销邮件 $this->app->when(NewsletterService::class) ->needs(Mailer::class) ->give(MarketingMailer::class); // 当其他类需要 Mailer 时,使用默认邮件 $this->app->bind(Mailer::class, DefaultMailer::class); } |
使用效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
class OrderService { public function __construct(Mailer $mailer) { // 这里自动注入 TransactionMailer } } class NewsletterService { public function __construct(Mailer $mailer) { // 这里自动注入 MarketingMailer } } class OtherService { public function __construct(Mailer $mailer) { // 这里自动注入 DefaultMailer } } |
2. 工厂类模式(推荐)
3. 条件绑定
根据运行时条件动态返回实例:
|
|
public function register() { $this->app->bind(Mailer::class, function ($app) { $request = $app['request']; // 根据用户类型选择邮件服务 if ($request->user()?->isVip()) { return new PriorityMailer($app['config']); } // 根据业务模块选择 if (str_contains($request->path(), 'marketing')) { return new MarketingMailer($app['config']); } // 默认邮件服务 return new DefaultMailer($app['config']); }); } |
4. 标签绑定 + 管理器模式
为多个实现打标签,通过管理器类统一管理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
public function register() { // 注册多个邮件实现并打标签 $this->app->bind('mailer.transaction', TransactionMailer::class); $this->app->bind('mailer.marketing', MarketingMailer::class); $this->app->bind('mailer.notification', NotificationMailer::class); $this->app->tag([ 'mailer.transaction', 'mailer.marketing', 'mailer.notification' ], 'mailers'); // 邮件管理器 $this->app->singleton(MailerManager::class, function ($app) { return new MailerManager($app->tagged('mailers')); }); } // 邮件管理器类 class MailerManager { private array $mailers; public function __construct(iterable $mailers) { foreach ($mailers as $mailer) { $this->mailers[get_class($mailer)] = $mailer; } } public function driver(string $driver): Mailer { return $this->mailers[$driver] ?? $this->mailers[TransactionMailer::class]; } public function forUser(User $user): Mailer { return $user->isPremium() ? $this->driver(PriorityMailer::class) : $this->driver(DefaultMailer::class); } } |
5. 配置驱动的动态绑定
根据配置文件动态选择实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
// config/mail.php return [ 'drivers' => [ 'transaction' => TransactionMailer::class, 'marketing' => MarketingMailer::class, 'default' => DefaultMailer::class, ], 'mappings' => [ 'App\\Services\\OrderService' => 'transaction', 'App\\Services\\NewsletterService' => 'marketing', ], ]; // 服务提供者 public function register() { $this->app->bind(Mailer::class, function ($app) { $config = $app['config']['mail']; $callerClass = $this->getCallerClass(); // 根据调用者类名选择驱动 $driver = $config['mappings'][$callerClass] ?? 'default'; $class = $config['drivers'][$driver]; return $app->make($class); }); } |
6. 实际业务场景示例
电商系统的邮件服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
public function register() { $this->app->when(OrderConfirmationService::class) ->needs(Mailer::class) ->give(TransactionMailer::class); $this->app->when(NewsletterService::class) ->needs(Mailer::class) ->give(MarketingMailer::class); $this->app->when(NotificationService::class) ->needs(Mailer::class) ->give(NotificationMailer::class); // 支付相关的特殊处理 $this->app->when(PaymentService::class) ->needs(Mailer::class) ->give(function ($app) { return $app->make(HighPriorityMailer::class); }); } // 使用方式完全一致,但底层实现不同 class OrderConfirmationService { public function send(Order $order, Mailer $mailer) { // 自动使用 TransactionMailer $mailer->send('emails.order-confirmation', [...]); } } |
5. 服务容器详解
服务容器是 Laravel 的核心组件,负责管理类依赖和执行依赖注入。它是一个"智能管理器",知道如何创建和管理对象。
服务提供者的绑定就是向容器注册构建规则:
|
|
public function register() { // 注册单例绑定 $this->app->singleton('helpSpot', function ($app) { return new HelpSpotService($app->make('redis')); }); } |
|
|
// 只能通过字符串解析 以下三行代码完全等效 $mailer = app('mailer'); $mailer = $this->app->make('mailer'); $mailer = App::make('mailer'); |
6. 延迟加载提供者
为了避免不必要的性能开销,Laravel 支持延迟服务提供者:
|
|
class CustomServiceProvider extends ServiceProvider { protected $defer = true; // 声明为延迟提供者 public function provides() { return [CustomService::class]; // 声明提供的服务 } public function register() { $this->app->singleton(CustomService::class, function () { return new CustomService(); }); } } |
7. 上下文绑定
可以根据使用场景绑定不同的实现:
8. 服务聚合与标签
可以使用标签将多个服务聚合,统一访问:
9. 服务解析方法
从服务容器解析对象有多种方式:
a. 使用 make 方法
|
|
$service = app()->make('service_name'); $service = $this->app['service_name']; // 在服务提供者内 $service = app('service_name'); |
b. 依赖注入(推荐)
|
|
public function __construct(UserRepository $users, MailService $mailer) { $this->users = $users; $this->mailer = $mailer; } |
10. 解析事件监听
可以在对象解析时触发特定逻辑:[todo 解析回调]
|
|
public function register() { $this->app->resolving(MyService::class, function ($service, $app) { // 当 MyService 被解析时执行 $service->initialize(); }); } |
12. 实践要点
a. 创建服务提供者
|
|
php artisan make:provider CustomServiceProvider |
b. 注册服务提供者
在 config/app.php 中注册:
|
|
'providers' => [ // 其他服务提供者... App\Providers\CustomServiceProvider::class, ], |
c. 服务提供者位置
自定义服务提供者存放在 app/Providers 目录下。
总结
Laravel 的服务容器、服务提供者和门面共同构成了框架强大的依赖注入系统。理解这些概念对于构建可维护、可测试的 Laravel 应用至关重要。服务提供者作为配置中心,服务容器作为依赖管理器,门面作为便捷接口,三者协同工作,为应用提供了极大的灵活性和可扩展性。
register() 用于"告诉容器有什么",boot() 用于"配置应用怎么用"。
应该放在 boot() 中的操作:
-
✅ 路由注册
-
✅ 事件监听器注册
-
✅ 视图 Composer 注册
-
✅ 中间件注册
-
✅ 数据库迁移/种子
-
✅ 资源发布(包开发)
-
✅ 宏扩展
-
✅ 任何需要其他服务的操作
应该放在 register() 中的操作:
-
✅ 服务绑定 (bind, singleton)
-
✅ 服务别名注册
-
✅ 简单的配置合并
- 注册阶段(register()):只是定义"如何创建"的规则
- 使用阶段(依赖注入时):才真正执行工厂函数,创建实例
「三年博客,如果觉得我的文章对您有用,请帮助本站成长」
共有 0 - Laravel服务/服务提供者/容器[beta2]